﻿using Microsoft.CSharp.RuntimeBinder;
using Aragorn.SOA.ORM;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using Aragorn.SOA.ORM.Lambda;
using Aragorn.SOA.ORM.DapperExtensions;


namespace Aragorn.SOA.ORM
{
    public class DbContext : IDisposable
    {
        private DbContextData Data
        {
            get;
            set;
        }

        public DbContext()
        {
            this.Data = new DbContextData();
        }

        public DbContext CommandTimeout(int timeout)
        {
            this.Data.CommandTimeout = new int?(timeout);
            return this;
        }

        public DbContext Commit()
        {
            this.TransactionAction(() => this.Data.Transaction.Commit());
            return this;
        }

        public DbContext ConnectionString(string connectionString, DatabaseType dabaseType = 0)
        {
            string str = "System.Data.SqlClient";
            switch (dabaseType)
            {
                case DatabaseType.MSSQLServer:
                    {
                        str = "System.Data.SqlClient";
                        break;
                    }
                case DatabaseType.Oracle:
                    {
                        str = "Oracle.DataAccess.Client";
                        break;
                    }
                case DatabaseType.OleDB:
                    {
                        str = "System.Data.OleDb";
                        break;
                    }
                case DatabaseType.SqlServerCe:
                    {
                        str = "System.Data.SqlServerCe.4.0";
                        break;
                    }
                case DatabaseType.MySql:
                    {
                        str = "MySql.Data.MySqlClient";
                        break;
                    }
                case DatabaseType.SQLite:
                    {
                        str = "System.Data.SQLite";
                        break;
                    }
                case DatabaseType.DB2:
                    {
                        str = "IBM.Data.DB2";
                        break;
                    }
                default:
                    {
                        throw new Exception("can not support the database type.");
                    }
            }
            this.Data.Connection = ConnectionFactory.CreateConnection(str, connectionString);
            this.Data.ConnectionString = connectionString;
            return this;
        }

        public DeleteCommand<T> Delete<T>(T entity)
        where T : class
        {
            return new DeleteCommand<T>(this.Data, entity);
        }

        public DeleteExpressionCommand<T> Delete<T>(Expression<Func<T, bool>> expression)
        where T : class
        {
            return new DeleteExpressionCommand<T>(this.Data, expression);
        }

        public DeleteExpressionCommand<T> Delete<T>()
        where T : class
        {
            return new DeleteExpressionCommand<T>(this.Data);
        }

        public void Dispose()
        {
            if (this.Data != null)
            {
                if (this.Data.Transaction != null)
                {
                    this.Data.Transaction.Rollback();
                    this.Data.Transaction.Dispose();
                }
                if (this.Data.Connection != null)
                {
                    if (this.Data.Connection.State != ConnectionState.Closed)
                    {
                        this.Data.Connection.Close();
                        if (this.Data.OnConnectionClosed != null)
                        {
                            this.Data.OnConnectionClosed(new ConnectionEventArgs(this.Data.Connection));
                        }
                    }
                    this.Data.Connection.Dispose();
                }
            }
        }

        public InsertCommand<T> Insert<T>(T entity)
        where T : class
        {
            return new InsertCommand<T>(this.Data, entity);
        }

        public MultiInsertCommand<T> Insert<T>(IEnumerable<T> entities)
        where T : class
        {
            return new MultiInsertCommand<T>(this.Data, entities);
        }

        public DbContext IsolationLevel(IsolationLevel isolationLevel)
        {
            this.Data.IsolationLevel = isolationLevel;
            if (this.Data.Connection.State != ConnectionState.Open)
            {
                if (this.Data.OnConnectionOpening != null)
                {
                    this.Data.OnConnectionOpening(new ConnectionEventArgs(this.Data.Connection));
                }
                this.Data.Connection.Open();
                if (this.Data.OnConnectionOpened != null)
                {
                    this.Data.OnConnectionOpened(new ConnectionEventArgs(this.Data.Connection));
                }
            }
            this.Data.Transaction = this.Data.Connection.BeginTransaction(isolationLevel);
            return this;
        }

        public DbContext OnConnectionClosed(Action<ConnectionEventArgs> action)
        {
            this.Data.OnConnectionClosed = action;
            return this;
        }

        public DbContext OnConnectionOpened(Action<ConnectionEventArgs> action)
        {
            this.Data.OnConnectionOpened = action;
            return this;
        }

        public DbContext OnConnectionOpening(Action<ConnectionEventArgs> action)
        {
            this.Data.OnConnectionOpening = action;
            return this;
        }

        public DbContext OnExecuted(Action<CommandEventArgs> action)
        {
            this.Data.OnExecuted = action;
            return this;
        }

        public DbContext OnExecuting(Action<CommandEventArgs> action)
        {
            this.Data.OnExecuting = action;
            return this;
        }

        public DbContext Rollback()
        {
            this.TransactionAction(() => this.Data.Transaction.Rollback());
            return this;
        }

        public T Select<T>(dynamic id)
        where T : class
        {
            return (T)(new SqlCommand(this.Data, "")).Get<T>(id);
        }

        public SqlCommand Select<T>(string sql, dynamic parameters = null)
        {
            return (SqlCommand)(new SqlCommand(this.Data, sql)).Parameter(parameters);
        }

        public SqlLamCommand<T> Select<T>()
        where T : class
        {
            return new SqlLamCommand<T>(this.Data);
        }

        public SqlLamCommand<T> Select<T>(Expression<Func<T, bool>> expression)
        where T : class
        {
            return new SqlLamCommand<T>(this.Data, expression);
        }

        public SqlCommand Sql(string sql, dynamic parameters = null)
        {
            return (SqlCommand)(new SqlCommand(this.Data, sql)).Parameter(parameters);
        }

        private void TransactionAction(Action action)
        {
            if (this.Data.Transaction == null)
            {
                return;
            }
            if (!this.Data.UseTransaction)
            {
                throw new Exception("Transaction support has not been enabled.");
            }
            action();
            this.Data.Transaction = null;
        }

        public UpdateCommand<T> Update<T>(T entity)
        where T : class
        {
            return new UpdateCommand<T>(this.Data, entity);
        }

        public UpdateSqlLamCommand<T> Update<T>()
        where T : class
        {
            return new UpdateSqlLamCommand<T>(this.Data);
        }

        public DbContext UseTransaction(bool useTransaction)
        {
            this.Data.UseTransaction = useTransaction;
            if (useTransaction)
            {
                if (this.Data.Connection.State != ConnectionState.Open)
                {
                    if (this.Data.OnConnectionOpening != null)
                    {
                        this.Data.OnConnectionOpening(new ConnectionEventArgs(this.Data.Connection));
                    }
                    this.Data.Connection.Open();
                    if (this.Data.OnConnectionOpened != null)
                    {
                        this.Data.OnConnectionOpened(new ConnectionEventArgs(this.Data.Connection));
                    }
                }
                this.Data.Transaction = this.Data.Connection.BeginTransaction();
            }
            else if (this.Data.Transaction != null)
            {
                this.Data.Transaction.Commit();
                this.Data.Transaction = null;
            }
            return this;
        }
    }
}